package media

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
	"ytDownload/entity"
	"ytDownload/logger"
	"ytDownload/tokenBucket"
	util "ytDownload/utils"
	"ytDownload/utils/cmd"
)

func handleSubtitle(startIdx, endIdx int, splitIdx []int) {

	saveDir := entity.GetSubtitleSaveDir()
	if len(saveDir) <= 0 {
		panic("请先设置保存目录")
	}

	stat, err := os.Stat(saveDir)
	if err != nil || !stat.IsDir() {
		logger.Errorf("字幕保存目录不存在")
		os.MkdirAll(saveDir, os.ModePerm)
	}

	/**
	 * 未下载的字幕索引号：32, 52, 72
	 */
	videoArr, _, errName := readCsvAsVideoStruct(startIdx, endIdx, splitIdx)

	defer func() {
		if r := recover(); r != nil {
			logger.Errorf("panic:%v", r)
		}
	}()

	totalCount := len(videoArr)
	wg := sync.WaitGroup{}
	wg.Add(totalCount)

	mx := sync.Mutex{}

	successCounter := util.IntCounter{}
	errCounter := util.IntCounter{}

	tb := tokenBucket.Create(3, 90*time.Second)

	for i := range videoArr {
		v := &videoArr[i]

		go func(v *VideoIdStruct) {
			tb.TryTakeOne()

			defer func() {
				if r := recover(); r != nil {
					logger.Errorf("panic:%v", r)
				}
				logger.Infof("[目前合计]下载字幕总数:%d,成功:%d,失败:%d",
					totalCount, successCounter.Get(), errCounter.Get())
				wg.Done()
			}()
			err := downSubtitle(v)
			if err != nil {
				logger.Errorf("[%s]下载字幕失败:%v", v.Name, err)
				appendErrName(&errName, v.Name, &mx)
				errCounter.Inc()
			} else {
				successCounter.Inc()
			}
		}(v)

	}

	wg.Wait()

	logger.Infof("\n\n!!!存在失败条目:\n[%s]\n\n", strings.Join(errName, ","))

}

func appendErrName(errName *[]string, name string, mx *sync.Mutex) {
	mx.Lock()
	defer mx.Unlock()
	*errName = append(*errName, name)
	return
}

func downSubtitle(v *VideoIdStruct) (err error) {
	/**
	 * yt-dlp --sleep-subtitles 30 --write-subs --write-auto-subs --skip-download
	 *   --sub-langs "en,zh-Hans" --sub-format "srt" -o "%(title)s.%(ext)s"  http://youtu.be/5YhR2zAkQmo
	 */
	res, err := cmd.ExecuCmdWithTargetDir(entity.GetSubtitleSaveDir(), "yt-dlp",
		"--sleep-subtitles", "30", "--write-subs", "--write-auto-subs",
		"--skip-download", "--sub-langs", "en,zh-Hans", "--sub-format", "srt",
		"-o", "%(title)s.%(ext)s",
		fmt.Sprintf("http://youtu.be/%s", v.VideoId))
	if err != nil {
		logger.Errorf("[%s]下载字幕失败:%v", v.Name, err)
	}

	resStr := res.String()
	logger.Infof("[%s]下载字幕结果:%s", v.Name, res.String())

	re := regexp.MustCompile(`\r?\n`)
	resArr := re.Split(resStr, -1)

	zhSuccess := false
	enSuccess := false
	for _, line := range resArr {
		if len(line) > 0 {
			if strings.Contains(line, "[download] Destination") {
				if strings.Contains(line, "zh-Hans.srt") {
					zhSuccess = true
				} else if strings.Contains(line, "en.srt") {
					enSuccess = true
				}
			}
		}
	}

	failName := ""
	if !zhSuccess {
		failName = "中文"
	}
	if !enSuccess {
		if len(failName) > 0 {
			failName += ","
		}
		failName += "英文"
	}

	if len(failName) > 0 {
		logger.Errorf("[%s]下载字幕失败:%s", v.Name, failName)
		err = fmt.Errorf("[%s]下载字幕失败:%s", v.Name, failName)
	}

	return
}

type SubTitleStruct struct {
	name    string
	enPath  string
	zhsPath string
}

func mergeSubtitles(startIdx, endIdx int) {

	saveDir := entity.GetSubtitleSaveDir()
	if len(saveDir) <= 0 {
		panic("请先设置保存目录")
	}

	// 遍历保存目录
	subTitleMap := make(map[string]*SubTitleStruct)
	subTitleNameArr := []string{}

	err := filepath.Walk(saveDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if !info.IsDir() {
			// 是文件，获取文件名，放入 map
			name := info.Name()
			logger.Infof("找到文件[%s]", name)
			fileName := ""
			isEn := false
			isZhs := false
			if strings.HasSuffix(name, ".en.srt") {
				// 获取文件名
				fileName = strings.TrimSuffix(name, ".en.srt")
				isEn = true
			} else if strings.HasSuffix(name, ".zh-Hans.srt") {
				// 获取文件名
				fileName = strings.TrimSuffix(name, ".zh-Hans.srt")
				isZhs = true
			}
			if strings.Index(fileName, "Handmade Hero ") == 0 {
				fileName = strings.TrimPrefix(fileName, "Handmade Hero ")
			}
			logger.Infof("文件名[%s]", fileName)

			if len(fileName) > 0 {

				if subTitleMap[fileName] == nil {
					subTitleNameArr = append(subTitleNameArr, fileName)
					subTitleMap[fileName] = &SubTitleStruct{
						name:    fileName,
						enPath:  "",
						zhsPath: "",
					}
				}
				if isEn {
					subTitleMap[fileName].enPath = path
				}
				if isZhs {
					subTitleMap[fileName].zhsPath = path
				}
			}

		}

		return nil
	})

	if err != nil {
		panic(err)
	}

	if len(subTitleNameArr) > 0 {
		sort.Strings(subTitleNameArr)
	} else {
		logger.Errorf("无数据")
		return
	}

	if endIdx > len(subTitleNameArr) {
		endIdx = len(subTitleNameArr)
	}
	subTitleNameArr = subTitleNameArr[startIdx:endIdx]

	mergePath := entity.GetSubtitleMergeOutputDir()
	stat, err := os.Stat(mergePath)
	if err != nil || !stat.IsDir() {
		logger.Errorf("字幕合并目录不存在")
		os.MkdirAll(mergePath, os.ModePerm)
	}

	for _, subtitleName := range subTitleNameArr {
		subtitle := subTitleMap[subtitleName]

		// 将两个字幕复制到合并目录中，去除前缀名
		err := copyFile(subtitle.enPath,
			filepath.Join(mergePath,
				// 英文字幕使用 zen 是为了保证合并的双语字幕在电脑上排在第一个
				fmt.Sprintf("%s.zen.srt", strings.TrimPrefix(subtitleName, "Handmade Hero "))))
		if err != nil {
			logger.Errorf("[%s]复制英文字幕失败:%v", subtitleName, err)
		}
		err = copyFile(subtitle.zhsPath,
			filepath.Join(mergePath,
				fmt.Sprintf("%s.zh-Hans.srt", strings.TrimPrefix(subtitleName, "Handmade Hero "))))
		if err != nil {
			logger.Errorf("[%s]复制中文字幕失败:%v", subtitleName, err)
		}

		err = mergeSubtitle(subtitle, mergePath)
		if err != nil {
			logger.Errorf("[%s]合并双语字幕失败:%v", subtitleName, err)
			continue
		}
		logger.Infof("[%s]合并完成", subtitleName)
	}

}

func copyFile(src, dest string) (err error) {
	srcFile, err := os.Open(src)
	if err != nil {
		return
	}
	defer srcFile.Close()

	destFile, err := os.Create(dest)
	if err != nil {
		return
	}
	defer destFile.Close()

	_, err = io.Copy(destFile, srcFile)

	return
}

type SrtContentStruct struct {
	index     int
	timestamp string
	content   string
}

func mergeSubtitle(subtitle *SubTitleStruct, mergePath string) (err error) {
	err = nil

	if len(subtitle.enPath) <= 0 || len(subtitle.zhsPath) <= 0 {
		err = fmt.Errorf("[%s]缺少字幕文件路径", subtitle.name)
		return
	}

	stat, err := os.Stat(subtitle.enPath)
	if stat == nil || err != nil || stat.IsDir() {
		err = fmt.Errorf("[%s]英文字幕文件不存在", subtitle.name)
		return
	}
	stat, err = os.Stat(subtitle.zhsPath)
	if stat == nil || err != nil || stat.IsDir() {
		err = fmt.Errorf("[%s]中文字幕文件不存在", subtitle.name)
		return
	}

	/**
	 * srt 文件格式
	 * 分为三段
	 * 1. 索引
	 * 2. 时间戳
	 * 3. 字幕内容
	 */
	// 读取英文字幕
	enSubtitleFile, err := os.Open(subtitle.enPath)
	if err != nil {
		err = fmt.Errorf("[%s]打开英文字幕文件失败:%v", subtitle.name, err)
		return
	}
	defer enSubtitleFile.Close()

	enSubArr := getSubtitleArr(enSubtitleFile)

	zhsSubtitleFile, err := os.Open(subtitle.zhsPath)
	if err != nil {
		err = fmt.Errorf("[%s]打开中文字幕文件失败:%v", subtitle.name, err)
		return
	}
	defer zhsSubtitleFile.Close()

	zhsSubArr := getSubtitleArr(zhsSubtitleFile)

	enOffset := 0
	zhsOffset := 0

	finalSubtitleStr := strings.Builder{}

	for enIdx, zhsIdx := 0, 0; enIdx < len(enSubArr) || zhsIdx < len(zhsSubArr); enIdx, zhsIdx = enIdx+1, zhsIdx+1 {

		var enSub *SrtContentStruct
		if enIdx < len(enSubArr) && enSubArr[enIdx] != nil {
			enSub = enSubArr[enIdx]
		}
		var zhsSub *SrtContentStruct
		if zhsIdx < len(zhsSubArr) && zhsSubArr[zhsIdx] != nil {
			zhsSub = zhsSubArr[zhsIdx]
		}

		if enSub != nil && zhsSub != nil {
			// 两者都未读完
			if enSub.timestamp != zhsSub.timestamp {
				// 时间戳不一致
				if enSub.timestamp < zhsSub.timestamp {
					// 英文时间戳小，先写英文字幕
					finalSubtitleStr.WriteString(fmt.Sprintf("%d\n%s\n%s\n\n",
						enSub.index+enOffset, enSub.timestamp, enSub.content))
					enOffset++
				} else {
					// 中文时间戳大
					finalSubtitleStr.WriteString(fmt.Sprintf("%d\n%s\n%s\n\n",
						zhsSub.index+zhsOffset, zhsSub.timestamp, zhsSub.content))
					zhsOffset++
				}
				logger.Errorf("[%s]时间戳不一致:%s,%s", subtitle.name, enSub.timestamp, zhsSub.timestamp)
			} else {
				// 时间戳一致
				finalSubtitleStr.WriteString(fmt.Sprintf("%d\n%s\n%s\n",
					enSub.index+enOffset, enSub.timestamp, fmt.Sprintf("%s%s", enSub.content, zhsSub.content)))
			}
		} else {
			// 其中一个字幕文件已读完
			sub := enSub
			if zhsSub != nil {
				sub = zhsSub
			}

			if sub != nil {
				finalSubtitleStr.WriteString(fmt.Sprintf("%d\n%s\n%s\n",
					sub.index+enOffset, sub.timestamp, sub.content))
			}
		}
	}

	if finalSubtitleStr.Len() < 0 {
		err = fmt.Errorf("[%s]无字幕数据", subtitle.name)
		return
	}

	err = os.WriteFile(filepath.Join(mergePath, fmt.Sprintf("%s.srt", subtitle.name)), []byte(finalSubtitleStr.String()), 0666)

	return
}

func getSubtitleArr(subtitleFile *os.File) []*SrtContentStruct {
	sc := bufio.NewScanner(subtitleFile)

	var subArr []*SrtContentStruct
	subT := &SrtContentStruct{}

	idx := 1
	for sc.Scan() {
		line := sc.Text()
		if strconv.Itoa(idx) == line {
			subT = &SrtContentStruct{
				index: idx,
			}
			idx++
			subArr = append(subArr, subT)
			// 时间戳
			if sc.Scan() {
				subT.timestamp = sc.Text()
			}
			// 字幕内容
			for sc.Scan() {
				line := sc.Text()
				if len(line) <= 0 || line == "\r" ||
					line == "\n" || line == "\r\n" || len(strings.TrimSpace(line)) <= 0 {
					break
				}
				subT.content += line + "\n"
			}
		}
	}

	return subArr
}

// 修复 yt-dlp 下载的字幕的时间轴
func fixTimeline() {
	// yt-dlp 下载的字幕时间轴有问题，上一行字幕的结束时间超过了下一行字幕的开始时间
	// 解决方式：让上一行的结束时间改为下一行的开始时间

	// 1. 读取 resource 目录下的 errSubtitle 文件
	errSubtitlePath := filepath.Join(entity.GetResourceDir(), "errSubtitle")

	filepath.Walk(errSubtitlePath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() {
			return nil
		}

		ext := filepath.Ext(path)
		if ext != ".srt" {
			return nil
		}

		filename := filepath.Base(path)
		logger.Infof("[修正字幕时间轴]开始修正:%s", filename)

		srtFile, err := os.Open(path)
		if err != nil {
			logger.Errorf("[修正字幕时间轴]打开文件报错:%v", err)
			return err
		}

		finalSubtitleStr := strings.Builder{}

		subtitleArr := getSubtitleArr(srtFile)
		for i := 0; i < len(subtitleArr); i++ {
			currentSrt := subtitleArr[i]

			if i == len(subtitleArr)-1 {
				break
			}
			nextSrt := subtitleArr[i+1]
			currentTimestamp := currentSrt.timestamp
			nextTimestamp := nextSrt.timestamp

			// 获取当前字幕的结束时间
			currentStopTime := strings.Split(currentTimestamp, " --> ")[1]
			// 获取下一行字幕的开始时间
			nextStartTime := strings.Split(nextTimestamp, " --> ")[0]

			stopTime, err := str2Int64(currentStopTime)
			if err != nil {
				logger.Errorf("error converting stop time: %v", err)
				return err
			}

			startTime, err := str2Int64(nextStartTime)
			if err != nil {
				logger.Errorf("error converting start time: %v", err)
				return err
			}

			replace := currentTimestamp
			// 结束时间大于开始时间，才修正
			if stopTime <= startTime {
				logger.Warnf("[修正字幕时间轴]不需要修正字幕idx:%d", currentSrt.index)
			} else {
				replace = strings.Replace(currentTimestamp, currentStopTime, nextStartTime, 1)
			}

			// subtitleArr[i].timestamp = replace

			finalSubtitleStr.WriteString(fmt.Sprintf("%d\n%s\n%s\n",
				currentSrt.index, replace, currentSrt.content))
		}

		err = os.WriteFile(filepath.Join(entity.GetResourceDir(), "fixSubtitle", filename), []byte(finalSubtitleStr.String()), 0666)
		if err != nil {
			logger.Errorf("[修正字幕时间轴]保存修正字幕报错:%v", err)
			return err
		}
		logger.Infof("[修正字幕时间轴]修正结束:%s", filename)

		return nil
	})

}

var timestampSplitRegex, _ = regexp.Compile("[:,]")

func str2Int64(str string) (int64, error) {
	if timestampSplitRegex == nil {
		return 0, errors.New("[str2Int64]正则初始化失败")
	}
	str = timestampSplitRegex.ReplaceAllString(str, "")

	// 字符串转数字
	i, err := strconv.ParseInt(str, 10, 64)
	if err != nil {
		return 0, err
	}

	return i, nil
}
